package gr.upatras.uniexamtimetable;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

/**
 * An <code>Exam</code> is a representation of any exam-like activity. 
 * 
 * @author spirosag
 */
public class Exam {

	private String name;
	private String code;
	private int semester;
	private String sector;

	/**
	 * The names of the examiners connected with exam 
	 */
	private List<Examiner> examiners = new ArrayList<Examiner>();

	/**
	 * The type of the exam. It could be oral, class etc 
	 */
	private String type;

	/**
	 * The periods an exam can't be held for multiple reasons.
	 */
	List<ExamForbiddenPeriod> forbiddenPeriods = 
		new ArrayList<ExamForbiddenPeriod>();

	/**
	 * The estimation of common students between two exams 
	 */
	List<CorrelationEstimation> correlationEstimations = 
		new ArrayList<CorrelationEstimation>();


	/**
	 * The classes required for the exam to be held.
	 */
	List<RequiredClass> requiredClasses = new ArrayList<RequiredClass>();


	/**
	 * Constructs an <code>Exam</code> with the default values.
	 */
	public Exam(){
		this.code = "sample code";
		this.name = "sample name";
		this.sector = null;
		this.semester = 0;
		this.type = "sample type";
	}


	/**
	 * Constructs an <code>Exam</code> object with the given parameters.
	 * 
	 * @param name						the name of the exam 
	 * @param code						the code of the exam
	 * @param semester					the semester the exam belongs to
	 * @param sector					the sector the exam belongs to	
	 * @param examiners					the examiners connected with the exam
	 * @param type						the type of the exam
	 * @param forbiddenPeriods			the periods an exam can't be held in
	 * 									for multiple reasons
	 * @param correlationEstimations	the estimation of common students
	 * 									between two exams
	 * @param requiredClasses			the classes required for the exam to be held
	 */
	public Exam(String name, String code, int semester, String sector,
			List<Examiner> examiners, String type,
			List<ExamForbiddenPeriod> forbiddenPeriods,
			List<CorrelationEstimation> correlationEstimations,
			List<RequiredClass> requiredClasses) {
		this.name = name;
		this.code = code;
		this.semester = semester;
		this.sector = sector;
		this.examiners = examiners;
		this.type = type;
		this.forbiddenPeriods = forbiddenPeriods;
		this.correlationEstimations = correlationEstimations;
		this.requiredClasses = requiredClasses;
	}

	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}


	/**
	 * @param name the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}


	/**
	 * @return the code
	 */
	public String getCode() {
		return code;
	}


	/**
	 * @param code the code to set
	 */
	public void setCode(String code) {
		this.code = code;
	}


	/**
	 * @return the semester
	 */
	public int getSemester() {
		return semester;
	}


	/**
	 * @param semester the semester to set
	 */
	public void setSemester(int semester) {
		this.semester = semester;
	}


	/**
	 * @return the sector
	 */
	public String getSector() {
		return sector;
	}


	/**
	 * @param sector the sector to set
	 */
	public void setSector(String sector) {
		this.sector = sector;
	}


	/**
	 * @return the examiners
	 */
	public List<Examiner> getExaminers() {
		return examiners;
	}


	/**
	 * @param examiners the examiners to set
	 */
	public void setExaminers(List<Examiner> examiners) {
		this.examiners = examiners;
	}


	/**
	 * @return the type
	 */
	public String getType() {
		return type;
	}


	/**
	 * @param type the type to set
	 */
	public void setType(String type) {
		this.type = type;
	}


	/**
	 * @return the forbiddenPeriods
	 */
	public List<ExamForbiddenPeriod> getForbiddenPeriods() {
		return forbiddenPeriods;
	}


	/**
	 * @param forbiddenPeriods the forbiddenPeriods to set
	 */
	public void setForbiddenPeriods(List<ExamForbiddenPeriod> forbiddenPeriods) {
		this.forbiddenPeriods = forbiddenPeriods;
	}


	/**
	 * @return the correlationEstimations
	 */
	public List<CorrelationEstimation> getCorrelationEstimations() {
		return correlationEstimations;
	}


	/**
	 * @param correlationEstimations the correlationEstimations to set
	 */
	public void setCorrelationEstimations(
			List<CorrelationEstimation> correlationEstimations) {
		this.correlationEstimations = correlationEstimations;
	}


	/**
	 * @return the requiredClasses
	 */
	public List<RequiredClass> getRequiredClasses() {
		return requiredClasses;
	}


	/**
	 * @param requiredClasses the requiredClasses to set
	 */
	public void setRequiredClasses(List<RequiredClass> requiredClasses) {
		this.requiredClasses = requiredClasses;
	}



	/**
	 * Prints all the characteristics of this object.  
	 */
	public void printStatus() {
		System.out.println("Exam name: " + name);
		System.out.println("Exam code: " + code);
		System.out.println("Exam type: " + type);

		if (sector != null) {
			System.out.println("Exam sector: " + sector);
		} else {
			System.out.println("Exam doesn't have a sector");
		}

		System.out.println("Exam type :" + type);

		System.out.println("Examiners connected with the exam:");
		for (int i = 0; i < examiners.size(); i++) {
			System.out.println(examiners.get(i).getName());
		}

		System.out.println("Required classes for the exam:");
		for (int i = 0; i < requiredClasses.size(); i++) {
			System.out.println("quantity: " +requiredClasses.get(i).getQuantity() 
					+ ", type: " + requiredClasses.get(i).getType() 
					+ ", completeUsage: " + requiredClasses.get(i).isCompleteUsage() 
					+ ", parallelExamination: " + requiredClasses.get(i).isParallelExamination() );
		}

		System.out.println("Correlation Estimations for the exam:");
		for (int i = 0; i < correlationEstimations.size(); i++) {
			System.out.println("exam1: " + correlationEstimations.get(i).getExamCode1() 
					+ ", exam2: " + correlationEstimations.get(i).getExamCode2() 
					+ ", correlation: " + correlationEstimations.get(i).getCorrelation());
		}

		System.out.println("Forbidden periods for the exam:");
		for (int i = 0; i < forbiddenPeriods.size(); i++) {
			System.out.println("Day: " + forbiddenPeriods.get(i).getDay() 
					+ ", start time: " + forbiddenPeriods.get(i).getStartTime() 
					+ ", end time: " + forbiddenPeriods.get(i).getEndTime());
		}
		System.out.println();

	}

	/**
	 * Returns the values of this object's variables in a single 
	 * string with explanatory labels. Overrides the java.lang.ObjecttoString()
	 * 
	 * @return a string with the values of this objects variables 
	 */
	public String toString() {
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.append(String.format("Exam name: %s, ", name));
		stringBuilder.append(String.format("Code: %s, ", code));
		stringBuilder.append(String.format("Sector: %s, ", sector));
		stringBuilder.append(String.format("Semester: %d,  ", semester));
		stringBuilder.append(String.format("Type: %s, ", type));

		stringBuilder.append(String.format("Examiners: "));
		for (int i = 0; i < examiners.size(); i++) {
			stringBuilder.append(examiners.get(i).getName());
			stringBuilder.append(", ");
		}

		stringBuilder.append(String.format("Correlation with exams: "));
		/* Appends the string :(exam:examCode,cor:correlation) */
		for (int i = 0; i < correlationEstimations.size(); i++) {
			if (correlationEstimations.get(i).getExamCode1() == code){

				stringBuilder.append(String.format("(Exam: %s,Cor: %s), ", 
						correlationEstimations.get(i).getExamCode2(),
						correlationEstimations.get(i).getCorrelation()));
			} else if (correlationEstimations.get(i).getExamCode2() == code) {
				stringBuilder.append(correlationEstimations.get(i).getExamCode1());
				stringBuilder.append(String.format("(Exam: %s, Cor: %s), ", 
						correlationEstimations.get(i).getExamCode1(),
						correlationEstimations.get(i).getCorrelation()));
			}

		}

		stringBuilder.append("Required classes: ");
		/* appends the string (type: type, quantity: quantity) */
		for (int i = 0; i < requiredClasses.size(); i++) {
			stringBuilder.append(String.format("(type: %s,quantity: %d)",
					requiredClasses.get(i).getType(), 
					requiredClasses.get(i).getQuantity()));				
		}

		stringBuilder.append("Forbidden periods:");
		/* appends the string (Day: YYYYMMDD,Start time: 1200,End time: 2400) */ 
		for (int i = 0; i < forbiddenPeriods.size(); i++) {
			stringBuilder.append(String.format("(Day: %d,Start time: +" +
					"%d,End time: %d)",
					forbiddenPeriods.get(i).getDay(),
					forbiddenPeriods.get(i).getStartTime(),
					forbiddenPeriods.get(i).getEndTime()));
		}

		return stringBuilder.toString();
	}

	
	/**
	 * Exports a <code>List</code> of <code>Exam</code> objects to a json file.
	 * 
	 * @param exams		the list of <code>Exam</code> objects to be exported 
	 * @param filename	the name of file to export to
	 */
	public static void exportToJson(List<Exam> exams, String filename) {

		BufferedWriter writer = null;
		Gson gson = new Gson();

		System.out.printf("Trying to export from %s ...%n", filename);
		try {
			writer = new BufferedWriter(new FileWriter(filename));

			/* ugly way to get the type of List<Room> */
			Type collectionType = new TypeToken<List<Exam>>(){}.getType();
			gson.toJson(exams, collectionType, writer);
		} catch (IOException e) {
			System.err.printf("Sorry, couldn't export to %s.%n", filename);
			e.printStackTrace();
		} finally {
			try {
				writer.close();
			} catch (IOException e){
				System.err.println("Sorry, couldn't properly close the buffered writer");
				e.printStackTrace();
			}
		}
		System.out.printf("Success: %d obcjects were imported.%n", exams.size());
	}	

	/* Imports a list of Exam objects from a json file */
	/**
	 * Imports a <code>List</code> of <code>Exam</code> objects from a json file.
	 * 
	 * @param filename	the name of the file to import from
	 * @return a <code>List</code> of <code>Exam</code> objects
	 */
	public static List<Exam> importFromJson(String filename) {

		BufferedReader reader = null;
		Gson gson = new Gson();
		List<Exam> exams = null;
		
		System.out.printf("Trying to import from %s ...%n", filename);
		
		try {
			reader = new BufferedReader(new FileReader(filename));

			/* ugly way to get the type of List<Room> */
			Type collectionType = new TypeToken<List<Exam>>(){}.getType();
			exams = gson.fromJson(reader, collectionType);
		} catch (IOException e) {
			System.out.printf("Sorry, couldn't import from %s.%n", filename);
			e.printStackTrace();
		} finally {
			try {
				reader.close();
			} catch (IOException e) {
				System.err.println("Sorry, couldn't close the buffered reader.");
				e.printStackTrace();
			}
		}
		
		System.out.printf("Success: %d obcjects were imported.%n", exams.size());
		return exams;		
	}	

}
